package com.itcoon.cloud.framework.mybatis.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.itcoon.cloud.framework.enums.CommonResponseCode;
import com.itcoon.cloud.framework.ex.Assertion;
import com.itcoon.cloud.framework.mybatis.core.IdDO;
import com.itcoon.cloud.framework.mybatis.core.Page;
import com.itcoon.cloud.framework.mybatis.mapper.XBaseMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;

public class XServiceImpl<M extends XBaseMapper<T>, T extends IdDO> extends ServiceImpl<M,T> implements XService<M, T> {

    private static final Logger log = LoggerFactory.getLogger(XServiceImpl.class);

    private TableInfo tableInfo;

    public synchronized TableInfo getTableInfo() {
        if(tableInfo == null){
            this.tableInfo = TableInfoHelper.getTableInfo(getEntityClass());
        }
        return this.tableInfo;
    }

    public XServiceImpl(M baseMapper) {
        super(baseMapper);
    }

    @Override
    public M getBaseMapper() {
        return super.getBaseMapper();
    }

    @Override
    public T getRealById(Serializable id) {
        return getBaseMapper().getRealById(id);
    }

    @Override
    public T getStrictById(Serializable id) {
        T t =  getBaseMapper().selectById(id);
        Assertion.assertNotNull(t).raiseCommon(CommonResponseCode.NOT_FOUND.getCode());
        return t;
    }

    @Override
    public T getRealStrictById(Serializable id) {
        T t = getBaseMapper().getRealById(id);
        log.error("resource not found for entity with name#{}", this.getTableInfo().getEntityType().getSimpleName());
        Assertion.assertNotNull(t).raise(CommonResponseCode.NOT_FOUND.getCode());
        return t;
    }

    @Override
    public List<T> listReal() {
        return getBaseMapper().listReal();
    }

    @Override
    public Page<T> pageReal(IPage<T> page) {
        return getBaseMapper().pageReal(page);
    }

    @Override
    public List<T> listRealByIds(Collection<? extends Serializable> ids) {
        return ids.isEmpty() ? Collections.emptyList() : getBaseMapper().listRealByIds(ids);
    }

    @Override
    public <R> T getOne(SFunction<T, R> sf, R r) {
        return generateEqQueryChain(sf, r).one();
    }

    @Override
    public <R> T getOneIn(SFunction<T, R> sf, Collection<R> cols) {
        return cols.isEmpty() ? null : lambdaQuery().in(sf, cols).one();
    }

    @Override
    public T getOne(Map<SFunction<T, ?>, ?> paramMap) {
        LambdaQueryChainWrapper<T> queryChain = generateMapQueryChain(paramMap);
        return queryChain == null ? null : queryChain.one();
    }

    @Override
    public T searchOne(Map<SFunction<T, ?>, ?> paramMap) {
        LambdaQueryChainWrapper<T> queryChain = generateMapSearchQueryChain(paramMap);
        return queryChain == null ? null : queryChain.one();
    }

    @Override
    public <R> boolean exists(SFunction<T, R> sf, R r) {
        int count = generateEqQueryChain(sf, r).count().intValue();
        return count > 0;
    }

    @Override
    public <R> boolean existsIn(SFunction<T, R> sf, Collection<R> cols) {
        return !cols.isEmpty() && generateInQueryChain(sf, cols).count() > 0;
    }

    @Override
    public boolean exists(Map<SFunction<T, ?>, ?> paramMap) {
        LambdaQueryChainWrapper<T> queryChain = generateMapQueryChain(paramMap);
        return queryChain != null && queryChain.count() > 0;
    }

    @Override
    public <R> List<T> list(SFunction<T, R> sf, R r) {
        return generateEqQueryChain(sf, r).list();
    }

    @Override
    public List<T> listLike(SFunction<T, String> sf,  String s) {
        return StringUtils.hasText(s) ? Collections.emptyList() : this.lambdaQuery().like(sf, s).list();
    }

    @Override
    public <R> List<T> listIn(SFunction<T, R> sf, Collection<R> cols) {
        return cols.isEmpty() ? Collections.emptyList() : generateInQueryChain(sf, cols).list();
    }

    @Override
    public List<T> list(Map<SFunction<T, ?>, ?> paramMap) {
        LambdaQueryChainWrapper<T> queryChain = generateMapQueryChain(paramMap);
        return queryChain == null ? Collections.emptyList() : queryChain.list();
    }

    @Override
    public List<T> searchAll(Map<SFunction<T, ?>, ?> paramMap) {
        LambdaQueryChainWrapper<T> queryChain = generateMapSearchQueryChain(paramMap);
        return queryChain == null ? Collections.emptyList() : queryChain.list();
    }

    @Override
    public <R> IPage<T> page(SFunction<T, R> sf, R r, IPage<T> page) {
        return generateEqQueryChain(sf, r).page(page);
    }

    @Override
    public <R> IPage<T> pageIn(SFunction<T, R> sf, Collection<R> cols, IPage<T> page) {
        return cols.isEmpty() ? Page.of(page, new ArrayList<T>()).setTotal(0) : generateInQueryChain(sf, cols).page(page);
    }

    @Override
    public IPage<T> page(Map<SFunction<T, ?>, ?> paramMap, IPage<T> page) {
        return generateMapQueryChain(paramMap).page(page);
    }

    @Override
    public IPage<T> searchPage(Map<SFunction<T, ?>, ?> paramMap, IPage<T> page) {
        return generateMapSearchQueryChain(paramMap).page(page);
    }

    @Override
    public <R> LambdaQueryChainWrapper<T> generateEqQueryChain(SFunction<T, R> sf, R r) {
        return lambdaQuery().eq(sf, r);
    }

    @Override
    public <R> LambdaQueryChainWrapper<T> generateInQueryChain(SFunction<T, R> sf, Collection<R> cols) {
        return lambdaQuery().in(sf, cols);
    }

    @Override
    public LambdaQueryChainWrapper<T> generateMapQueryChain(Map<SFunction<T, ?>, ?> paramMap) {
        LambdaQueryChainWrapper<T> queryChain = lambdaQuery();
        for (Map.Entry<SFunction<T, ?>, ?> entry : paramMap.entrySet()) {
            Object obj = entry.getValue();
            if(obj != null){
                if(obj instanceof Collection){
                    Collection<?> cols = (Collection<?>) obj;
                    if(!cols.isEmpty()){
                        queryChain.in(entry.getKey(), cols);
                    }
                }else{
                    queryChain.eq(entry.getKey(), obj);
                }
            }
        }
        return queryChain;
    }

    @Override
    public LambdaQueryChainWrapper<T> generateMapSearchQueryChain(Map<SFunction<T, ?>, ?> paramMap){
        LambdaQueryChainWrapper<T> queryChain = lambdaQuery();
        for (Map.Entry<SFunction<T, ?>, ?> entry : paramMap.entrySet()) {
            Object obj = entry.getValue();
            if(obj != null){
                if(obj instanceof Collection){
                    Collection<?> cols = (Collection<?>) obj;
                    if(!cols.isEmpty()){
                        queryChain.in(entry.getKey(), cols);
                    }
                }else if(obj instanceof String){
                    queryChain.like(entry.getKey(), obj);
                }else{
                    queryChain.eq(entry.getKey(), obj);
                }
            }
        }
        return queryChain;
    }

    @Override
    public <R> boolean updateById(SFunction<T, R> setSf, R r, Serializable id) {
        return lambdaUpdate().set(setSf, r).eq(T::getId, id).update();
    }

    @Override
    public boolean updateById(Map<SFunction<T, ?>, ?> setMap, Serializable id) {
        LambdaUpdateChainWrapper<T> updateChain = lambdaUpdate();
        for (Map.Entry<SFunction<T, ?>, ?> entry : setMap.entrySet()) {
            updateChain.set(entry.getValue() != null, entry.getKey(), entry.getValue());
        }
        return updateChain.eq(T::getId, id).update();
    }

    @Override
    public <R, U> boolean update(SFunction<T, R> setSf, R r, SFunction<T, U> selectSf, U u) {
        return lambdaUpdate().set(setSf, r).eq(selectSf, u).update();
    }

    @Override
    public boolean update(Map<SFunction<T, ?>, ?> setMap, Map<SFunction<T, ?>, ?> selectMap) {
        LambdaUpdateChainWrapper<T> updateChain = lambdaUpdate();
        for (Map.Entry<SFunction<T, ?>, ?> entry : setMap.entrySet()) {
            updateChain.set(entry.getValue() != null, entry.getKey(), entry.getValue());
        }
        Collection<Map.Entry<SFunction<T, ?>, ?>> values = selectMap.entrySet().stream().filter(v->v.getValue() instanceof Collection).collect(Collectors.toSet());
        for (Map.Entry<SFunction<T, ?>, ?> entry : values) {
            Collection<?> cols = (Collection<?>) entry.getValue();
            if(!cols.isEmpty()){
                updateChain.in(entry.getKey(), cols);
            }else{
                return true;
            }
        }
        values = selectMap.entrySet().stream().filter(v->!(v.getValue() instanceof Collection)).collect(Collectors.toSet());
        values.forEach(v->updateChain.eq(v.getKey(), v.getValue()));
        return updateChain.update();
    }

    @Override
    public <R> boolean remove(SFunction<T, R> sf, R r) {
        return lambdaUpdate().eq(sf, r).remove();
    }

    @Override
    public <R> boolean remove(SFunction<T, R> sf, Collection<R> cols) {
        return lambdaUpdate().in(sf, cols).remove();
    }

    @Override
    public boolean remove(Map<SFunction<T, ?>, ?> map) {
        LambdaUpdateChainWrapper<T> updateChain = lambdaUpdate();
        Collection<Map.Entry<SFunction<T, ?>, ?>> values = map.entrySet().stream().filter(v->v.getValue() instanceof Collection).collect(Collectors.toSet());
        for (Map.Entry<SFunction<T, ?>, ?> entry : values) {
            Collection<?> cols = (Collection<?>) entry.getValue();
            if(!cols.isEmpty()){
                updateChain.in(entry.getKey(), cols);
            }else{
                return true;
            }
        }
        values = map.entrySet().stream().filter(v->!(v.getValue() instanceof Collection)).collect(Collectors.toSet());
        values.forEach(v->updateChain.eq(v.getKey(), v.getValue()));
        return updateChain.remove();
    }

    @Override
    public boolean removeRealById(Serializable id) {
        return getBaseMapper().removeRealById(id);
    }

    @Override
    public int removeRealByIds(Collection<? extends Serializable> ids) {
        return ids.isEmpty() ? 0 : getBaseMapper().removeRealByIds(ids);
    }


    private boolean isLogicTable(TableInfo table){
        TableFieldInfo fieldInfo = table.getLogicDeleteFieldInfo();
        return fieldInfo != null;
    }



}
